#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/string.h>


#define MYCDEV_NAME "mycdev_simple"
#define MYCDEV_CLASS "mycdev_class"
#define CDEV_NUM 1

#define IOCTL_MODE_MAGIC 'x'
#define IOCTL_MODE_1 _IO(IOCTL_MODE_MAGIC, 1)
#define IOCTL_MODE_2 _IO(IOCTL_MODE_MAGIC, 2)


static struct class *mycdev_class;
static struct cdev cdevs[CDEV_NUM];
static struct device devices[CDEV_NUM];
#define STR_BUFF_SIZE 64
static char str_buff[STR_BUFF_SIZE] = "init string!";
static char str_buff_copy[STR_BUFF_SIZE] = "init string!";
#define DEFAULT_IOCTL_MODE 2 
static int ioctl_mode = IOCTL_MODE_2;

static dev_t dev_num;
 
 
static int strLength(const char *s);
static int mycdev_open(struct inode *inode, struct file *file);
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len , loff_t *pos);
static ssize_t mycdev_write(struct file *, const char __user *buff, size_t len, loff_t *pos);
static int mycdev_release(struct inode *inode, struct file *file);
static long mycdev_ioctl(struct file *filep, unsigned int command, unsigned long arg);
 
static struct file_operations op_f={
	.read = mycdev_read,
	.write = mycdev_write,
	.open = mycdev_open,
	.release = mycdev_release,
    .unlocked_ioctl = mycdev_ioctl
};

static ssize_t mycdev_show(struct device *dev,
                  struct device_attribute *attr, char *buf)   //cat命令时,将会调用该函数
{
    int ret = 0, mode = DEFAULT_IOCTL_MODE;

    if (ioctl_mode == IOCTL_MODE_2) {
        mode = 2; 
    } else if (ioctl_mode == IOCTL_MODE_1) {
        mode = 1;
    } else {
        mode = DEFAULT_IOCTL_MODE;
        printk("mycdev_show EIVNAL, set default valule %d\n", DEFAULT_IOCTL_MODE);
    }

    ret = sprintf(buf, "%d\n", mode);
    printk("mycdev_show %d, ret=%d\n", mode, ret);
    
    return ret;
}

static ssize_t mycdev_store(struct device *dev,
                 struct device_attribute *attr,
                 const char *buf, size_t len)   //echo命令时,将会调用该函数.
{
    int mode;

    sscanf(buf, "%d",&mode);
    if (mode == 2) {
        ioctl_mode = IOCTL_MODE_2;
        printk("current mode: read order\n");
    } else if (mode == 1) {
        ioctl_mode = IOCTL_MODE_1;
        printk("current mode: read inverted order\n");
    } else {
        ioctl_mode = -1;
        printk("EINVAL\n");
        return -EINVAL;
    }
    
    printk("mycdev_store iotcl_mode=%d\n", ioctl_mode);
    return len;
}
static DEVICE_ATTR(mycdev_simple0, S_IWUSR|S_IRUSR, mycdev_show, mycdev_store);  
//定义一个名字为my_device_test的设备属性文件

 
static int __init simple_chardev_init(void)
{
	int i,retVal;
    dev_t curr_devnum; 
 
    retVal = alloc_chrdev_region(&dev_num,0,CDEV_NUM,MYCDEV_CLASS);
    if(retVal != 0)
    {
    	printk("alloc_chrdev_region error %d\n",retVal);
    	return retVal;
    }
 
    mycdev_class = class_create(THIS_MODULE,MYCDEV_CLASS);
 
    for(i = 0; i < CDEV_NUM; i++)
    {
    	cdev_init(&cdevs[i], &op_f);
    	cdevs[i].owner = THIS_MODULE;
 
    	curr_devnum = MKDEV(MAJOR(dev_num),MINOR(dev_num)+i);
    	retVal = cdev_add(&cdevs[i], curr_devnum, 1);
        printk("cdev_add retVal %d\n",retVal);
 
    	devices[i] = *(device_create(mycdev_class, NULL, curr_devnum, NULL, MYCDEV_NAME "%d", i));
        printk("device_create %p\n",&devices[i]);

        if(sysfs_create_file(&(devices[i].kobj), &dev_attr_mycdev_simple0.attr)) {
            printk("sysfs_create_file failed\n");
            return -1;
        }
    }
	return retVal;
}
 
static void __exit simple_chardev_exit(void)
{
    dev_t _devnum;
    int i;
 
    for(i = 0; i < CDEV_NUM; i++)
    {
        _devnum = MKDEV(MAJOR(dev_num), MINOR(dev_num)+i);
        device_destroy(mycdev_class, _devnum);
        cdev_del(&(cdevs[i]));
    }
    class_destroy(mycdev_class);
    unregister_chrdev_region(dev_num, CDEV_NUM);
    
    printk("simple_chardev_exit\n");
    return;
}
 
 
static int mycdev_open(struct inode *inode, struct file *file)
{
	printk("call mycdev_open\n");
	return 0;
}
 
static ssize_t mycdev_read(struct file *file, char __user *buff, size_t len , loff_t *pos)
{
    int count, retVal, i, j;
	

    count = strLength(str_buff);
    if (ioctl_mode == IOCTL_MODE_1) {
        memset(str_buff_copy, 0, STR_BUFF_SIZE);
        for (i = 0, j = count-1; i < count; i++, j--){
            str_buff_copy[i] = str_buff[j];
        }

	    retVal = copy_to_user(buff,str_buff_copy,count);
    } else {
	    retVal = copy_to_user(buff,str_buff,count);
    }

    if(retVal == -EFAULT)
    {
        printk("mycdev_read error\n");
        return -EFAULT;
    }
    
    printk("call mycdev_read %d %ld\n", count, len);
	return count;
}
 
static ssize_t mycdev_write(struct file *file, const char __user *buff, size_t len, loff_t *pos)
{
    int retVal;
 
    printk("call mycdev_write\n");

    if (len > STR_BUFF_SIZE)
        return -EFAULT;


    retVal = copy_from_user(str_buff, buff, len);
    if(retVal == -EFAULT)
    {
        printk("mycdev_write error\n");
        return -EFAULT;
    }

    memset(str_buff+len, 0, STR_BUFF_SIZE-len);
    printk("recv %s\n", str_buff);
    return len;
}
 
static int mycdev_release(struct inode *inode, struct file *file)
{
	printk("call mycdev_release\n");
	return 0;
}

static long mycdev_ioctl(struct file *filep, unsigned int command, unsigned long arg)
{
    switch(command) {

        case IOCTL_MODE_1:
        case IOCTL_MODE_2:
            ioctl_mode = command;
            break;

        default:
            return -EINVAL;

    }

    return 0;
}

static int strLength(const char *s)
{
	int len;
	len=0;
	while((*s)!=0)
	{
		s++;
		len++;
	}
	return len;
}
 
 
module_init(simple_chardev_init);
module_exit(simple_chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxxx");
MODULE_DESCRIPTION("111");
